package ru.amse.tsyganov.jumleditor.storing;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import ru.amse.tsyganov.jumleditor.model.ActivityGraph;
import ru.amse.tsyganov.jumleditor.model.State;
import ru.amse.tsyganov.jumleditor.model.Transition;
import ru.amse.tsyganov.jumleditor.view.ActivityGraphView;
import ru.amse.tsyganov.jumleditor.view.StateView;
import ru.amse.tsyganov.jumleditor.view.TransitionView;

public class Storage {
	
	public static void write(
			ActivityGraphView<? extends ActivityGraph> activityGraph,
			String path) throws IOException, ParserConfigurationException, TransformerException {
		
		if (activityGraph == null || path == null) {
			throw new IllegalArgumentException();
		}
		
		File outputFile = new File(path);
		
		if (outputFile.exists()) {
			outputFile.delete();
		}
		outputFile.createNewFile();
		
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document root = builder.newDocument();
		
		XmlWriteVisitor xmlWriteVisitor = new XmlWriteVisitor(root, false);
		activityGraph.accept(xmlWriteVisitor);
		xmlWriteVisitor.setVisitTransitions(true);
		activityGraph.accept(xmlWriteVisitor);
		
		DOMSource source = new DOMSource(root);
		
		FileOutputStream os = new FileOutputStream(outputFile);
		StreamResult result = new StreamResult(os);
		
		TransformerFactory transFactory = TransformerFactory.newInstance();
		Transformer transformer = transFactory.newTransformer();
		transformer.transform(source, result);
	}
	
	public static ActivityGraphView<ActivityGraph> read(String path) 
		throws ParserConfigurationException, SAXException, IOException, 
			InstantiationException, IllegalAccessException, ClassNotFoundException, 
			SecurityException, NoSuchMethodException, IllegalArgumentException, 
			InvocationTargetException {
		
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document document = builder.parse(path);
		
		Map<Integer, State> idToStateMap = new HashMap<Integer, State>();
		Map<Integer, Transition> idToTransitionMap = new HashMap<Integer, Transition>();
		
		ActivityGraph activityGraph = new ActivityGraph();
		
		NodeList stateNodeList = document.getElementsByTagName("stateModel");
		
		for (int i = 0; i < stateNodeList.getLength(); i++) {
			Node stateNode = stateNodeList.item(i);
			String className = stateNode.getFirstChild().
				getAttributes().getNamedItem("class").getNodeValue();
			
			State state = (State) Class.forName(className).newInstance();
			activityGraph.addState(state);
			
			state.readFromXml(stateNode.getFirstChild());
			
			int id = Integer.parseInt(stateNode.getAttributes().
					getNamedItem("id").getNodeValue());
			
			idToStateMap.put(id, state);
		}
		
		NodeList transitionNodeList = document.getElementsByTagName("transition");
		
		for (int i = 0; i < transitionNodeList.getLength(); i++) {
			Node transitionNode = transitionNodeList.item(i);
			
			int sourceStateId = Integer.parseInt(
					transitionNode.getAttributes().
					getNamedItem("sourceState").getNodeValue());
			
			int targetStateId = Integer.parseInt(
					transitionNode.getAttributes().
					getNamedItem("targetState").getNodeValue());
			
			State source = idToStateMap.get(sourceStateId);
			State target = idToStateMap.get(targetStateId);
			
			Transition t = new Transition(
					source.getStateVertex(),
					target.getStateVertex());
			
			t.addBindings();
			
			int id = Integer.parseInt(transitionNode.getAttributes().
					getNamedItem("id").getNodeValue());
			
			idToTransitionMap.put(id, t);
		}
		
		ActivityGraphView<ActivityGraph> activityGraphView = 
			new ActivityGraphView<ActivityGraph>(activityGraph);
		
		NodeList stateViewNodeList = document.getElementsByTagName("stateView");
		
		for (int i = 0; i < stateViewNodeList.getLength(); i++) {
			Node stateViewNode = stateViewNodeList.item(i);
			String className = stateViewNode.getFirstChild().
				getAttributes().getNamedItem("class").getNodeValue();
			
			int id = Integer.parseInt(stateViewNode.getAttributes().
					getNamedItem("id").getNodeValue());
			
			State state = idToStateMap.get(id);
			
			Class stateViewClass = Class.forName(className);
			Constructor c = stateViewClass.getConstructor(new Class[] {
					state.getClass(), 
					ActivityGraphView.class});
			
			StateView stateView = (StateView) c.newInstance(new Object[] {
				state,
				activityGraphView
			});
			
			stateView.readFromXml(stateViewNode.getFirstChild());
			
			activityGraphView.getModelViewMap().getStatesMap().put(state, stateView);
		}
		
		NodeList transitionViewNodeList = document.getElementsByTagName("transitionView");
		
		for (int i = 0; i < transitionViewNodeList.getLength(); i++) {
			Node transitionViewNode = transitionViewNodeList.item(i);
			String className = transitionViewNode.getFirstChild().
				getAttributes().getNamedItem("class").getNodeValue();
			
			int id = Integer.parseInt(transitionViewNode.getAttributes().
					getNamedItem("id").getNodeValue());
			
			Transition transition = idToTransitionMap.get(id);
			
			TransitionView<Transition> transitionView = 
				new TransitionView<Transition>(transition, activityGraphView);
			
			transitionView.readFromXml(transitionViewNode.getFirstChild());
			
			activityGraphView.getModelViewMap().getTransitionsMap().put(transition, transitionView);
		}
		
		return activityGraphView;
	}
}
